/*
	log_proc.h	Log file processing
	Copyright (c) 2001, 2002, 2004, 2005, 2010 Kriang Lerdsuwanakij
	email:		lerdsuwa@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "config.h"

#include <cstdlib>
#include <exception>
#include <stdexcept>
#include <string>
#include <fstream>
#include <iomanip>

#include "board.h"
#include "order.h"
#include "proginfo.h"
#include "gtstream.h"

#ifdef _
#undef _
#endif

#ifdef N_
#undef N_
#endif

#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) x

struct game_log {
	int move_queue[60];
	int move_queue_color[60];
	int time_queue[60];	// Unit 0.01 sec
	int num_move_queue;

	int time_pass_queue[60];
	int num_pass_queue;

	byte_board_info board;
	int black_score;
	int white_score;

	bool timeout;
	bool resign;
	bool mutual;
	enum game_format { format_IOS, format_GGS };
	game_format format;

	game_log(int bs, int ws): 
		num_move_queue(0), num_pass_queue(0), board(),
		black_score(bs), white_score(ws),
		timeout(false), resign(false), mutual(false) {}
	void copy_board(const byte_board_type *b) { board = b; }
	void copy_board(const byte_board_info &b) { board = b; }
	void push_move(int pos, int color, int time = 0) {
		move_queue[num_move_queue] = pos;
		move_queue_color[num_move_queue] = color;
		time_queue[num_move_queue] = time;
		num_move_queue++;
	}
	void push_pass(int time) {
		time_pass_queue[num_pass_queue] = time;
		num_pass_queue++;
	}
	void reset_move() { num_move_queue = 0; }

	void set_random() {}
	void set_black_name(const std::string&, int /*idx*/, int /*len*/) {}
	void set_white_name(const std::string&, int /*idx*/, int /*len*/) {}
};

#define VAL_BLANK	-1
#define VAL_WHITE	-2
#define VAL_BLACK	-3

inline int	from_ascii(const std::string &s, int i)
{
	if (s[i] == ' ') {
		if (s[i+1] == ' ')
			return VAL_BLANK;
		else
			return s[i+1]-'0';
	}
	else if (s[i] == '(' && s[i+1] == ')')
		return VAL_WHITE;
	else if (s[i] == '#' && s[i+1] == '#')
		return VAL_BLACK;
	else {
		return (s[i]-'0')*10 + s[i+1]-'0';
	}
}

template <class game_log_T, class T>
void	process_line_IOS(const std::string &s, T &t, bool need_end)
{
	int	black_score = from_ascii(s, 22);
	int	white_score = from_ascii(s, 48);

	bool	timeout = false;
	bool	resign = false;

	if (s[11] == 't')
		timeout = true;
	else if (s[11] == 'r')
		resign = true;

	if (s[11] != 'e' && need_end)	// Game not properly ended
		return;

				// Old IOS format
	if (s[65] == '+') {
		int index = 65;

		game_log_T game(black_score, white_score);
		game.format = game_log::format_IOS;

		if (timeout)
			game.timeout = true;
		if (resign)
			game.resign = true;

		game.copy_board(&board_begin);

		game.set_black_name(s, 13, 8);
		game.set_white_name(s, 39, 8);

		do {
			int player = (s[index] == '+') ? BLACK : WHITE;
			int ipos = from_ascii(s, index+1);
			int pos = xy_to_pos(ipos/10 -1, ipos%10 -1);
			game.push_move(pos, player);
			index += 3;
		} while (s[index] == '+' || s[index] == '-');

		process_game(t, game);
	}
				// Old IOS format, resigned game
	else if (s[66] == '+')
		;
				// Old IOS format with 'K'
	else if (s[65] == 'K' && s[70] == '+') {
		int index = 70;

		game_log_T game(black_score, white_score);
		game.format = game_log::format_IOS;

		if (timeout)
			game.timeout = true;
		if (resign)
			game.resign = true;

		game.copy_board(&board_begin);

		game.set_black_name(s, 13, 8);
		game.set_white_name(s, 39, 8);

		do {
			int player = (s[index] == '+') ? BLACK : WHITE;
			int ipos = from_ascii(s, index+1);
			int pos = xy_to_pos(ipos/10 -1, ipos%10 -1);
			game.push_move(pos, player);
			index += 3;
		} while (s[index] == '+' || s[index] == '-');

		process_game(t, game);
	}
				// New IOS format
	else if (s[65] == 'K' && s[72] == ' ') {
		byte_board_type init_board;

		int num_move_queue = 0;
		int move_queue[60];
		int index = 73;

				// New IOS format support random games
				// Setup the initial board
		for (int pos = 0; pos < 64; ++pos, index += 2) {
			int val = from_ascii(s, index);
			switch (val) {
				case VAL_WHITE:
					init_board[pos] = WHITE;
					break;
				case VAL_BLACK:
					init_board[pos] = BLACK;
					break;
				case VAL_BLANK:
					init_board[pos] = EMPTY;
					break;
				default:
					init_board[pos] = EMPTY;
					move_queue[val-1] = pos;
					num_move_queue++;
			}
		}

		byte_board_info board(&init_board);
		bool random = false;
		if (board.get_num_move() == 0) {
			if (init_board[D4] != WHITE)
				return;
			if (init_board[D5] != BLACK)
				return;
			if (init_board[E4] != BLACK)
				return;
			if (init_board[E5] != WHITE)
				return;
		}
		else
			random = true;

				// Scores will be filled later
		game_log_T game(0, 0);
		game.format = game_log::format_IOS;

		if (timeout)
			game.timeout = true;
		if (resign)
			game.resign = true;

		game.copy_board(board);
		if (random)
			game.set_random();

		game.set_black_name(s, 13, 8);
		game.set_white_name(s, 39, 8);

				// Assume that black moves first
		int init_player = BLACK;
		int player = init_player;
		int i;
		for (i = 0; i < num_move_queue; ++i) {
				// Fail, wrong assumption about to get
				// the first move
			if (!board.can_play(player, move_queue[i])) {
				break;
			}

			game.push_move(move_queue[i], player);

			board.place_piece(player, move_queue[i]);
			if (board.can_play(switch_color(player)))
				player = switch_color(player);
		}
		if (i == num_move_queue) {
			black_score = board.board_black_score();
			white_score = board.board_white_score();
			adjust_score(black_score, white_score);
		}
		else {
				// Assume that white moves first

			game.reset_move();
			board = game.board;

			init_player = WHITE;
			player = init_player;
			for (int i = 0; i < num_move_queue; ++i) {
				if (!board.can_play(player, move_queue[i]))
					abort();

				game.push_move(move_queue[i], player);

				board.place_piece(player, move_queue[i]);
				if (board.can_play(switch_color(player)))
					player = switch_color(player);
			}
			black_score = board.board_black_score();
			white_score = board.board_white_score();
			adjust_score(black_score, white_score);
		}

				// Now collect pattern info
		game.black_score = black_score;
		game.white_score = white_score;

		process_game(t, game);
	}
}

// Input i = position after "(;"
// Return position after ";)" or -1 upon error
template <class game_log_T, class T>
int	process_game_GGS(const std::string &s, int i, T &t,
					 bool need_end, int /*game_number*/)
{
	byte_board_type init_board;

	int size = s.size();
	bool result = false;	// No result yet

				// Scores will be filled later
 	game_log_T game(0, 0);
	game.format = game_log::format_GGS;

	try {
		while (i < size) {
			char c1 = s.at(i);
			char c2 = s.at(i+1);

			if (c1 == ';' && c2 == ')') {
							// No result ???
				if (!result)
					return -1;

				process_game(t, game);
				return i+2;
			}
	
			char c3 = s.at(i+2);		// At least 3 chars needed

							// Sorted according to
							// order of appearance
			if (c1 == 'G' && c2 == 'M' && c3 == '[') {
				i += 3;
				if (s.compare(i, 8, "Othello]"))
					return -1;
				i += 8;
			}
			else if (c1 == 'P' && c2 == 'B' && c3 == '[') {
				i += 3;
				int j = i;
				while (s.at(i) != ']')
					i++;
				game.set_black_name(s, j, i-j);
				i++;
			}
			else if (c1 == 'P' && c2 == 'W' && c3 == '[') {
				i += 3;
				int j = i;
				while (s.at(i) != ']')
					i++;
				game.set_white_name(s, j, i-j);
				i++;
			}
			else if (c1 == 'T' && c2 == 'Y' && c3 == '[') {
				i += 3;
				char c;
				bool found8 = false;
				while ((c = s.at(i)) != ']') {
					if (c == 'a')		// Anti game
						return -1;
					else if (c == '8') {
						if (found8)	// 88 game
							return -1;
						else
							found8 = true;
						i++;
					}
					else if (c == 'r') {	// Random game
						game.set_random();
						break;		// skip remaining
					}
					else if (c >= '0' && c <= '9')	// Not 8x8 board
						return -1;
					else
						i++;
				}

				while (s.at(i) != ']')
					i++;
				i++;
			}
			else if (c1 == 'R' && c2 == 'E' && c3 == '[') {
				i += 3;

							// Read score difference
				int diff_sign = (s.at(i) == '-') ? -1: 1;
				if (s.at(i) == '-' || s.at(i) == '+')
					i++;
				int diff = 0;
				while (s.at(i) >= '0' && s.at(i) <= '9') {
					diff = diff*10 + (s.at(i)-'0');
					i++;
				}
				diff = diff * diff_sign;

							// Check game result
				while (s.at(i) != ']') {
					if (s.at(i) == 't') {	// Timeout
						game.timeout = true;
						if (need_end)
							return -1;
					}
					if (s.at(i) == 'r') {	// Resign
						game.resign = true;
						if (need_end)
							return -1;
					}
					if (s.at(i) == 's') {	// Mutual score
						game.mutual = true;
						if (need_end)
							return -1;
					}
					i++;
				}

				while (s.at(i) != ']')
					i++;
				i++;
							// Correct when diff == 0
				game.black_score = diff > 0 ? diff : 0;
				game.white_score = diff < 0 ? -diff : 0;
//std::cout << diff << ' ' << game.black_score << ' ' << game.white_score << std::endl;
		
				result = true;
			}
			else if (c1 == 'B' && c2 == 'O' && c3 == '[') {
				i += 3;
				if (s.at(i) != '8' || s.at(i+1) != ' ')	// Bad board size
					return -1;
				i += 2;

							// GGS format support random games
							// Setup the initial board
				for (int pos = 0; pos < 64; ++pos) {

					while (s.at(i) == ' ')
						i++;

					switch (s.at(i)) {
						case 'O':
							init_board[pos] = WHITE;
							break;
						case '*':
							init_board[pos] = BLACK;
							break;
						case '-':
							init_board[pos] = EMPTY;
							break;
						default:
							return -1;	// Unknown character
					}
					i++;	
				}
				game.copy_board(&init_board);
								// No need to process color
								// to move.  It's handled by
								// B[...] or W[...]

				while (s.at(i) != ']')
					i++;
				i++;
			}
			else if (c1 == 'B' && c2 == '[') {
				i += 2;

								// FIXME: More strict check
				int pos = -1;
				if (s.at(i) == 'P' || s.at(i) == 'p')
					;	// Pass
				else if (s.at(i) >= 'A' && s.at(i) <= 'H')
					pos = xy_to_pos(s.at(i)-'A', s.at(i+1)-'1');
				else if (s.at(i) >= 'a' && s.at(i) <= 'h')
					pos = xy_to_pos(s.at(i)-'a', s.at(i+1)-'1');
				else
					return -1;

				int time = 0;
				while (s.at(i) != ']' && s.at(i) != '/')
					i++;
				if (s.at(i) == '/') {		// Score
					i++;
					while (s.at(i) != ']' && s.at(i) != '/')
						i++;
					if (s.at(i) == '/') {	// Time
						i++;

						double mult = 100.0;
						bool found_decimal = false;
						while ((s.at(i) >= '0' && s.at(i) <= '9')
						       || s.at(i) == '.') {
			       				if (s.at(i) == '.') {
								if (found_decimal)
									return -1;
								found_decimal = true;
							}
							else {
								time = time*10 + s.at(i)-'0';
								if (found_decimal)
									mult /= 10.0;
							}
							i++;
						}

						time = static_cast<int>(time * mult +0.5);

						while (s.at(i) != ']')
							i++;
					}
				}

				if (pos != -1)
					game.push_move(pos, BLACK, time);
				else
					game.push_pass(time);

				i++;			// Skip past ']'
			}
			else if (c1 == 'W' && c2 == '[') {
				i += 2;

								// FIXME: More strict check
				int pos = -1;
				if (s.at(i) == 'P' || s.at(i) == 'p')
					;	// Pass
				else if (s.at(i) >= 'A' && s.at(i) <= 'H')
					pos = xy_to_pos(s.at(i)-'A', s.at(i+1)-'1');
				else if (s.at(i) >= 'a' && s.at(i) <= 'h')
					pos = xy_to_pos(s.at(i)-'a', s.at(i+1)-'1');
				else
					return -1;

				int time = 0;
				while (s.at(i) != ']' && s.at(i) != '/')
					i++;
				if (s.at(i) == '/') {		// Score
					i++;
					while (s.at(i) != ']' && s.at(i) != '/')
						i++;
					if (s.at(i) == '/') {	// Time
						i++;

						double mult = 100.0;
						bool found_decimal = false;
						while ((s.at(i) >= '0' && s.at(i) <= '9')
						       || s.at(i) == '.') {
			       				if (s.at(i) == '.')
								found_decimal = true;
							else {
								time = time*10 + s.at(i)-'0';
								if (found_decimal)
									mult /= 10.0;
							}
							i++;
						}

						time = static_cast<int>(time * mult +0.5);

						while (s.at(i) != ']')
							i++;
					}
				}

				if (pos != -1)
					game.push_move(pos, WHITE, time);
				else
					game.push_pass(time);

				i++;			// Skip past ']'
			}
			else {
				if (isspace(c1))
					i++;
				else {
							// Skip the remaining property
							// also ignore garbage char after
							// ']' like W[a2]//5.53]
					while (s.at(i) != '[' && s.at(i) != ']')
						i++;

					if (s.at(i) == '[') {	// Skip property
						i++;		// Move after '['
						while (s.at(i) != ']')
							i++;
						i++;		// Move after ']'
					}
					else
						i++;		// Move after ']'
				}
			}
		}
							// Premature end-of-line
		return -1;
	}
	catch (std::out_of_range &) {
		return -1;
	}
}

template <class game_log_T, class T>
void	process_line_GGS(const std::string &s, T &t, bool need_end)
{
	int i = 0;
	int size = s.size();
	int game_number = 0;

						// Synchro-rand games have
						// 2 games per line
	while (i+1 < size) {
		if (s[i] == '(' && s[i+1] == ';') {
			++game_number;
			i = process_game_GGS<game_log_T, T>(s, i+2, t, need_end, game_number);
			if (i == -1)		// Erroneous line
				return;
		}
		else
			i++;
	}
}

template <class game_log_T, class T>
void	process_line(const std::string &s, T &t, bool need_end)
{
	if ((s[0] == '(' && s[1] == ';')	// Old GGS
	    || (s[2] == '(' && s[3] == ';'))	// New GGS supporting synchro rand
		process_line_GGS<game_log_T, T>(s, t, need_end);
	else
		process_line_IOS<game_log_T, T>(s, t, need_end);
}

template <class game_log_T, class T>
void	process_file(const char *f, T &t, bool need_end = true, bool output = true)
{
	if (output)
		gtout(std::cout, _("Processing %$\n")) << f;

	std::ifstream ifs(f);
	if (!ifs) {
		gtstream bufstr;
		gtout(bufstr, _("cannot open file %$")) << f;
		throw std::runtime_error(bufstr.str());
	}

	int line = 0;
	for ( ; ; ) {
		if (output && line % 100 == 99) {
			std::cout << '.';
			std::cout.flush();
		}
		line++;

		std::string buffer;
		getline(ifs, buffer);
					// End of file
		if (!ifs)
			break;

		try {
			process_line<game_log_T, T>(buffer, t, need_end);
		}
		catch (...) {
			if (output)
				gtout(std::cerr, _("\n%$: fail at line %$\n")) 
					<< prog_name << line;
		}
	}

	if (output)
		std::cout << '\n';
}

#undef _
#undef N_
